<?php
/**
	@brief Updates all repos for a project
 */
function vcs_update($project)
{
	//grab data from DB as needed
	$repos = dbquery('select * from repos where `project` = \'' . $project . '\'');		
	while($repo = mysql_fetch_object($repos))
	{
		if($repo->vcs == VCS_SVN)
			vcs_svn_update($repo);
	}
}

/**
	@brief Displays the browser for a project with 0 or more repos of different types
 */
function vcs_browser($project)
{
	global $g_tplvars;
	
	//Display each repository
	$repos = dbquery('select * from repos where `project` = \'' . $project . '\'');		
	$str = '';
	while($repo = mysql_fetch_object($repos))
	{
		switch($repo->vcs)
		{
		case VCS_SVN:
			$g_tplvars['listing'] = vcs_svn_browser($repo, $repo->cachedrev);
			break;
		default:
			$g_tplvars['listing'] = '';
			break;
		}
		
		$g_tplvars['dirname'] = basename($repo->url);
		$str .= templatize('../templates/projects-vcs-browser-dir.html');
	}
	
	$g_tplvars['tree'] = $str;
	return templatize('../templates/projects-vcs-browser.html');
}

/**
	@brief Gets the number of commits for a given user in a project
	
	@param repo Repository ID
	@param uid User ID
 */
function vcs_commit_count($project, $uid)
{
	//Do each repository
	$repos = dbquery('select * from repos where `project` = \'' . $project . '\'');		
	$count = 0;
	while($repo = mysql_fetch_object($repos))
	{
		switch($repo->vcs)
		{
		case VCS_SVN:
			$count += vcs_repo_commit_count($repo, $uid);
			break;
		default:
			break;
		}
	}
	
	return $count;
}

/**
	@brief Gets the number of commits for a specific user
 */
function vcs_repo_commit_count($repo, $uid)
{
	return mysql_num_rows(dbquery('select `id` from revisions where `repo` = \'' . $repo->id . '\' and `author` = \'' . $uid . '\''));
}

/**
	@brief Displays changes made in a specific revision
	
	@param repo Repository ID
	@param rev Revision number
 */
function vcs_changes($repo, $rev)
{
	//Look up the repo
	$r = dbquery('select * from repos where `id` = \'' . $repo . '\'');	
	if(mysql_num_rows($r) != 1)
	{
		//todo: error
		return;
	}
	$rep = mysql_fetch_object($r);
	
	//and format it nicely depending on the VCS
	switch($rep->vcs)
	{
	case VCS_SVN:
		return vcs_svn_changes($rep, $rev);
	default:
		break;
	}
}

/**
	@brief Displays history for a project with 0 or more repos of different types
	
	History from multiple VCSes is printed out in sequence for now but will eventually be interleaved.
	
	@param project Project ID
	@param newest Date of newest revision to show
	@param count Number of revisions to show
 */
function vcs_history($project, $newest, $count)
{
	global $g_tplvars;
	
	//Display each repository
	$repos = dbquery('select * from repos where `project` = \'' . $project . '\'');		
	$str = '';
	$revshown = 0;
	while($repo = mysql_fetch_object($repos))
	{
		$g_tplvars['repo'] = $repo->id;
		$g_tplvars['id'] = $project;
		
		//get recent commits
		//TODO: interleave
		$revs = dbquery('select * from revisions where `repo` = \'' . $repo->id . '\' and `mtime` < \'' . $newest . '\' order by `mtime` desc limit ' . $count);
		while($rev = mysql_fetch_object($revs))
		{
			if($revshown > $count)
				break;
			
			//Look up the author
			$a = dbquery('select `name` from users where `uid` = \'' . $rev->author . '\'');
			$g_tplvars['authorname'] = '';
			if(mysql_num_rows($a))
				$g_tplvars['authorname'] = mysql_fetch_object($a)->name;
		
			$maxlen = 64;	//todo: be configurable
		
			$commitlog = $rev->msg;
			if(strlen($commitlog) > $maxlen)
				$commitlog = substr($commitlog, 0, $maxlen - 3) . '...';
		
			$g_tplvars['revnum'] = $rev->revnum;
			$g_tplvars['author'] = $rev->author;
			$g_tplvars['comment'] = $commitlog;
			$g_tplvars['date'] = format_date($rev->mtime);
		
			$str .= templatize('../templates/projects-vcs-history-item.html');
			
			$revshown ++;
		}
	}
	
	$g_tplvars['history'] = $str;
	return templatize('../templates/projects-vcs-history.html');
}

/**
	@brief Returns the contents of a file at a specific revision
 */
function vcs_file_contents($repo, $rev, $file)
{
	//Look up the repo
	$r = dbquery('select * from repos where `id` = \'' . $repo . '\'');	
	if(mysql_num_rows($r) != 1)
	{
		//todo: error
		return;
	}
	$rep = mysql_fetch_object($r);
	
	//and format it nicely depending on the VCS
	switch($rep->vcs)
	{
	case VCS_SVN:
		return vcs_svn_file_contents($rep, $rev, $file);
	default:
		break;
	}
}

/**
	@brief Returns the diff for a file at a specific revision
		
	@param repo Repository ID
	@param rev Revision number
	@param oldrev Old revision number
	@param file Path to the file
 */
function vcs_file_diff($repo, $rev, $oldrev, $file)
{
	//Look up the repo
	$r = dbquery('select * from repos where `id` = \'' . $repo . '\'');	
	if(mysql_num_rows($r) != 1)
	{
		//todo: error
		return;
	}
	$rep = mysql_fetch_object($r);
	
	//and format it nicely depending on the VCS
	switch($rep->vcs)
	{
	case VCS_SVN:
		return vcs_svn_file_diff($rep, $rev, $oldrev, $file);
	default:
		break;
	}
}

////////////////////////////////////////////////////////////////////////////////

/**
	@brief Updates a Subversion repository's metadata
	@param repo Row from the repos table
 */
function vcs_svn_update($repo)
{
	//Open up the repository
	$cached = $repo->cachedrev;
	$hrepo = svn_repos_open($repo->url);
	if(!$hrepo)
		return false;
	$hfs = svn_repos_fs($hrepo);
	if(!$hfs)
		return false;
	
	//Check if anything needs updating
	$diskrev = svn_fs_youngest_rev($hfs);
	if($diskrev <= $cached)
		return true;
		
	//Create revisions for each one we missed
	$logs = svn_log('file://' . $repo->url, $cached+1, $diskrev);
	$date = 0;
	foreach($logs as $log)
	{
		$rev = intval($log['rev']);
		$author = sanitize_db($log['author']);
		$msg = sanitize_db(trim($log['msg']));
		$date = strtotime($log['date']);
		$updated = count($log['paths']);
		
		//Look up the author
		$a = dbquery('select `uid` from users where `name` = \'' . $author . '\'');
		$aid = -1;
		if(mysql_num_rows($a))
			$aid = mysql_fetch_object($a)->uid;
		
		//Add to the database
		dbquery(
				'insert into revisions(`repo`,`revnum`,`author`,`msg`,`mtime`) '.
				'values(\'' . $repo->id . '\', \'' . $rev . '\', \'' . $aid . '\', \'' . $msg . '\', \'' . $date . '\')'
			);
			
		//TODO: parse commit messages for commands etc
	}
	
	//Update the repo and project to reflect the new stuff
	dbquery('update repos set `cachedrev` = \'' . $diskrev . '\' where `id` = \'' . $repo->id . '\' limit 1');
	dbquery('update projects set `mtime` = \'' . $date . '\' where `id` = \'' . $repo->project . '\' limit 1');
}

/**
	@brief Displays the browser for a Subversion repository
	@param repo Row from the repos table
	@param rev Revision number
 */
function vcs_svn_browser($repo, $rev)
{
	global $g_tplvars;
	$g_tplvars['rev'] = $rev;
	
	//Open up the repository
	$cached = $repo->cachedrev;
	$hrepo = svn_repos_open($repo->url);
	if(!$hrepo)
		return false;
	$hfs = svn_repos_fs($hrepo);
	if(!$hfs)
		return false;
	$hroot = svn_fs_revision_root($hfs, $rev);
	
	return vcs_svn_dobrowser($hroot, '/');
}

/**
	@brief Displays the browser for a path in a Subversion repository
 */
function vcs_svn_dobrowser($hroot, $parent)
{
	global $g_tplvars;
	$listing = svn_fs_dir_entries($hroot,$parent);
	$tree = '';
	
	$tree = array();
	
	foreach($listing as $f => $t)
	{
		//get full file name
		$fname = $parent;
		if($parent[strlen($parent) - 1] != '/')
			$fname .= '/';
		$fname .= $f;
			
		if($t == 2)
			$tree[$fname] = vcs_svn_dobrowser($hroot, $fname);
		else
		{
			$g_tplvars['fname'] = $f;
			$g_tplvars['pathname'] = $fname;
			$g_tplvars['fid'] = 'id_' . md5($fname);
			if(substr_count($f, '.cpp') > 0)
				$g_tplvars['class'] = 'cpp';
			else
				$g_tplvars['class'] = 'file';	//todo: fancy stuff
			$tree[$fname] = templatize('../templates/projects-vcs-exbrowser-line.html');
		}
	}
	
	//Sort alphabetically
	$stree = '';
	ksort($tree);
	foreach($tree as $t)
		$stree .= $t;
	
	if($parent == '/')
		return $stree;

	$g_tplvars['class'] = 'dir';
	$g_tplvars['fname'] = basename($parent);
	$g_tplvars['fid'] = 'id_' . md5($parent);		//cryptographic security isnt necessary here and sha1 is excessively long
	$g_tplvars['tree'] = $stree;
	return templatize('../templates/projects-vcs-exbrowser.html');
}

/**
	@brief Displays summary of changes made in a specific version of the repository.
	
	@param repo Row from the repos table
	@param rev Revision number
 */
function vcs_svn_changes($repo, $rev)
{
	global $g_tplvars;
	
	//Grab log info
	$logs = svn_log('file://' . $repo->url, $rev, $rev);
	$paths = $logs[0]['paths'];
	
	//Open up the repository
	$cached = $repo->cachedrev;
	$hrepo = svn_repos_open($repo->url);
	if(!$hrepo)
		return false;
	$hfs = svn_repos_fs($hrepo);
	if(!$hfs)
		return false;
	$hroot = svn_fs_revision_root($hfs, $rev);
	
	$g_tplvars['rev'] = $rev;
	$g_tplvars['repo'] = $repo->id;
	
	//Show the changes
	$pwd = array();
	$stack = array();
	$stacktop = 0;
	foreach($paths as $changes)
	{
		//Parse the file name
		$fname = substr($changes['path'], 1);		//trim off leading /
		$pathlist = explode('/', $fname);
		$depth = count($pathlist);
		$base = $pathlist[$depth - 1];
		
		//Find the common prefix of us and the last directory
		$prefix = array();
		for($i = 0; $i < count($pwd); $i++)
		{
			if($pwd[$i] != $pathlist[$i])
				break;
			array_push($prefix, $pwd[$i]);
		}
		
		//TODO: save mod state for when we leave the dir
		if(svn_fs_is_dir($hroot, $fname))
			continue;
		
		//If we left a directory, back up
		if(count($prefix) < count($pwd))
		{
			for($i = count($pwd) - 1; $i >= count($prefix); $i--)
			{
				$g_tplvars['fname'] = $pwd[$i];
				$g_tplvars['listing'] = array_pop($stack);
				$stacktop--;
				$str = templatize('../templates/projects-vcs-changes-dir.html');
				$stack[$stacktop] .= $str;
			}
			$pwd = $prefix;
		}
		
		//Entering a new directory? Add it to pwd
		for($i = count($prefix); $i < (count($pathlist) - 1); $i++)
		{
			array_push($stack, '');
			$stacktop ++;
			array_push($pwd, $pathlist[$i]);
		}

		//Render the stuff
		$g_tplvars['mod'] = $changes['action'];
		$g_tplvars['fname'] = $base;
		$g_tplvars['class'] = 'file';
		$g_tplvars['path'] = $changes['path'];
		if(!isset($stack[$stacktop]))
			$stack[$stacktop] = '';
		$stack[$stacktop] .= templatize('../templates/projects-vcs-changes-file.html');
	}
	
	//If we left a directory, back up
	for($i = count($pwd) - 1; $i >= 0; $i--)
	{
		$g_tplvars['fname'] = $pwd[$i];
		$g_tplvars['listing'] = array_pop($stack);
		$stacktop--;
		$str = templatize('../templates/projects-vcs-changes-dir.html');
		$stack[$stacktop] .= $str;
	}
	
	return $stack[0];
}

/**
	@brief Returns the contents of a file in a Subversion repository.
	
	@param rep Repository row from the database
	@param rev Revision number
	@param file File name
 */
function vcs_svn_file_contents($rep, $rev, $file)
{
	return svn_cat('file://' . $rep->url . '/' . $file, $rev);
}

/**
	@brief Returns a Subversion diff for a file at two different revisions
	
	@param rep Repository row from the database
	@param rev New revision number
	@param oldrev Old revision number
	@param file File name
 */
function vcs_svn_file_diff($rep, $rev, $oldrev, $file)
{
	$fname = 'file://' . $rep->url . '/' . $file;
	$hfiles = svn_diff($fname, $oldrev, $fname, $rev);
	$diffs = $hfiles[0];
	$ret = '';
	while(1)
	{
		$s = fgets($diffs);
		if(!$s)
			break;
		$ret .= $s;
	}
	return $ret;
}
?>
